#pragma once

#include <vector>

#include "../inner_pre.h"
#include "../event_loop.h"
#include "udp_message.h"

namespace evnetplus {

class EventLoop;
class FdChannel;
class PipeEventWatcher;
class DNSResolver;

namespace udp {

// We can use this class to create a UDP client.
// The typical usage is :
//      1. Create a UDPClient object
//      2. Set the message callback
//      3. Use UDPClient::Create() to create a socket structure
//      4. Use UDPClient::SendMsg(...) to send messages to remote server
//      5. Call UDPClient::Destroy() to cancel socket structure
//
class UDPClient : public std::enable_shared_from_this<UDPClient> {
public:
    using MessageCallback = std::function<void(const MessagePtr& msg)>;
    using MessageDottedCallback = std::function<void(const std::string&/*remote_ip*/,int/*remote_port*/,Buffer*)>;

public:
    // @brief The constructor of the class
    // @param[in] loop - The EventLoop runs this object
    // @param[in] remote_addr - The remote server address with format "host:port"
    //  If the host is not IP, it will automatically do the DNS resolving asynchronously
    // @param[in] name -
    explicit UDPClient(EventLoop* loop,
                       const std::string& remote_addr/*host:port*/,
                       const std::string& name);
    ~UDPClient();

    // @brief Try to establish a structure with remote server asynchronously
    void Create();

    // @brief Disconnect from the remote server.
    void Destroy();
public:
    // Set the message callback to handle the messages from remote server
    void SetMessageCallback(const MessageCallback& cb);
    void SetMessageCallback(const MessageDottedCallback& cb);

    // Send message to remote server
    void SendMsg(const slice& message);
    void SendMsg(const MessagePtr& message) {
        SendMessage(message);
    }

public:
    const std::string& remote_addr() const {
        return remote_addr_;
    }
    const std::string& local_addr() const {
        return local_addr_;
    }
    const std::string& name() const {
        return name_;
    }
    EventLoop* loop() const {
        return loop_;
    }
    void set_creating_timeout(Duration timeout) {
        creating_timeout_ = timeout;
    }
    Duration creating_timeout() const {
        return creating_timeout_;
    }

private:
    void CreateSocket();
    void HandleError();
    void onSendHandle();
    void onReceivedHandle();
    void OnDNSResolved(const std::vector <struct in_addr>& addrs);
    std::string StatusToString() const;

private:
    enum Status { kNotCreated, kDNSResolving, kDNSResolved, kCreated };
    Status status_;
    EventLoop* loop_;
    std::string local_addr_;
    std::string remote_addr_;
    std::string remote_host_;
    int remote_port_;
    std::string name_;
    evnetplus_socket_t sockfd_;

    struct sockaddr_storage raddr_;

    std::unique_ptr<FdChannel> read_channel_;
    std::unique_ptr<PipeEventWatcher> read_watcher_;
    std::unique_ptr<PipeEventWatcher> write_watcher_;
    std::shared_ptr<DNSResolver> dns_resolver_;

    Buffer input_buffer_;
    Buffer output_buffer_;

    Duration creating_timeout_ = Duration(3.0);

    MessageCallback message_callback_fn_;
    MessageDottedCallback message_dotted_callback_fn_;
};

typedef std::shared_ptr<UDPClient> UDPClientPtr;

}

}

